package nosql.property;

import java.util.Map;
import java.util.UUID;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

/**
 * PropertySetBuilder can generate an instance of PropertySet according to JSON string.
 * @author Yexi Jiang <http://sites.google.com/site/yexijiang/>
 *
 */
public class PropertySetBuilder {
	
	private static int currentPropertyId = 0;
	private String entityId;
	private PropertySet propertySet;
	
	public PropertySetBuilder(){
		entityId = UUID.randomUUID().toString();
		propertySet  = new PropertySet();
	}
	
	/**
	 * Parse the JSON document and convert into property set by parsing the JSON string.
	 * @param json
	 * @throws ParseException 
	 */
	public PropertySet parse(String s) throws ParseException, PropertySetTranslateException{
		parse(s, false, null, -1, 0);
		return propertySet;
	}
	
	@SuppressWarnings("unchecked")
	private void parse(Object s, boolean hasParent, String parentName, int parentId, int parentType) throws ParseException, PropertySetTranslateException{
		Object obj = null;
		JSONParser parser = new JSONParser();
		try{
			obj = parser.parse(s.toString());
		} catch (ParseException e){
			//	simple type
			int type = checkType(s);
			Property property = new Property(parentName, entityId, parentId, type, s.toString());
			propertySet.add(property);
			return;
		}
		if(obj instanceof JSONObject){
			JSONObject jsonObject = (JSONObject)obj;
			for(Object subObj : jsonObject.entrySet()){
				int propertyId = ++currentPropertyId;
				Map.Entry entry = (Map.Entry)subObj;
				String key = entry.getKey().toString();
				if(key.contains("."))
					throw new PropertySetTranslateException("Illegal property name:" + key + " in " + s);
				Object value = entry.getValue();
				parse(value, true, key, propertyId, Property.UNDEFINED);
				int type = checkType(obj);
				if(type == Property.ARRAY){
					if(value instanceof String)
						type = Property.ARRAY_STRING;
					else if(value instanceof Boolean)
						type = Property.ARRAY_BOOLEAN;
					else if(value instanceof Long)
						type = Property.ARRAY_LONG;
					else if(value instanceof Double)
						type = Property.ARRAY_DOUBLE;
					else
						type = Property.ARRAY_COMPLEX;
				}
				//	add parent property
				if(hasParent == true){
					Property parentProperty = new Property(parentName, entityId, parentId, type, ""+propertyId);
					propertySet.add(parentProperty);
				}
			}
		}
		else if(obj instanceof JSONArray){
			JSONArray jsonArray = (JSONArray)obj;
			
			for(int i = 0; i < jsonArray.size(); ++i){
				int propertyId = ++currentPropertyId;
				Object value = jsonArray.get(i);
				parse(value, true, "array" + i, propertyId, Property.UNDEFINED);
				int type = checkType(obj);
				if(type == Property.ARRAY){
					if(value instanceof String)
						type = Property.ARRAY_STRING;
					else if(value instanceof Boolean)
						type = Property.ARRAY_BOOLEAN;
					else if(value instanceof Long)
						type = Property.ARRAY_LONG;
					else if(value instanceof Double)
						type = Property.ARRAY_DOUBLE;
					else
						type = Property.ARRAY_COMPLEX;
				}
				//	add parent property
				if(hasParent == true){
					Property parentProperty = new Property(parentName, entityId, parentId, type, ""+propertyId);
					propertySet.add(parentProperty);
				}
			}
		}
		else{
			//	simple type
			int type = checkType(s);
			Property property = new Property(parentName, entityId, parentId, type, s.toString());
			propertySet.add(property);
		}
	}
	
	private int checkType(Object obj){
		int type = Property.UNDEFINED;
		if(obj instanceof String)
			type = Property.STRING;
		else if(obj instanceof Boolean)
			type = Property.BOOLEAN;
		else if(obj instanceof Long)
			type = Property.LONG;
		else if(obj instanceof Double)
			type = Property.DOUBLE;
		else if(obj instanceof JSONObject)
			type = Property.COMPLEX;
		else if(obj instanceof JSONArray)
			type = Property.ARRAY;
		//	create property
		return type;
	}
	
	/**
	 * Convert the json into property set.
	 * @param json
	 * @return
	 * @throws ParseException
	 */
	public PropertySet store(String json) throws ParseException{
		JSONParser parser = new JSONParser();
		Object obj = parser.parse(json);
		String strObj = obj.toString();
		obj = parser.parse(strObj);
		if(obj instanceof JSONObject){
			JSONObject jsonObj = (JSONObject)obj;
			for(Object subObj : jsonObj.entrySet()){
				Map.Entry entry = (Map.Entry)subObj;
				String key = entry.getKey().toString();
				String value = entry.getValue().toString();
				int type = Property.UNDEFINED;
				try{
					Object parsedValue = parser.parse(value);
					if (parsedValue instanceof JSONObject)
						type = Property.COMPLEX;
					else if (parsedValue instanceof JSONArray)
						type = Property.ARRAY;
					else if (parsedValue instanceof Long)
						type = Property.LONG;
					else if (parsedValue instanceof Boolean)
						type = Property.BOOLEAN;
					else if (parsedValue instanceof Double)
						type = Property.DOUBLE;
				} catch(ParseException e){
					type = Property.STRING;
					value = "\"" + value + "\"";
				}
				Property property = new Property(key, entityId, currentPropertyId++, type, value);
				propertySet.add(property);
			}
		}
		else if(obj instanceof JSONArray){
			JSONArray jsonObj = (JSONArray)obj;
			for(int i = 0; i < jsonObj.size(); ++i){
				String key = "array" + i;
				String value = jsonObj.get(i).toString();
				int type = Property.UNDEFINED;
				try{
					Object parsedValue = parser.parse(value);
					if ((parsedValue instanceof JSONObject)	|| (parsedValue instanceof JSONArray))
						type = Property.ARRAY_COMPLEX;
					else if (parsedValue instanceof Long)
						type = Property.ARRAY_LONG;
					else if (parsedValue instanceof Boolean)
						type = Property.ARRAY_BOOLEAN;
					else if (parsedValue instanceof Double)
						type = Property.ARRAY_DOUBLE;
				} catch(ParseException e){
					type = Property.ARRAY_STRING;
					value = "\"" + value + "\"";
				}
				Property property = new Property(key, entityId, currentPropertyId++, type, value);
				propertySet.add(property);
			}
		}
		
		return propertySet;
	}
	
}
